package edu.cornell.cs.lsi.projone.service;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.impl.LogFactoryImpl;

import edu.cornell.cs.lsi.projone.common.LSIConstants;
import edu.cornell.cs.lsi.projone.common.util.AppUtil;
import edu.cornell.cs.lsi.projone.model.SessionTableData;

public class RPCServerService extends Thread {

	private final static Log LOGGER = LogFactoryImpl
			.getLog(RPCServerService.class);

	private static final BrickManager brickMgr = new BrickManager();
	private String ipAddr = "";
	private int port = 0;
	public static boolean isthreadStarted = false;
	private static RPCServerService rpcServerService = new RPCServerService();

	public RPCServerService() {

		try {
			ipAddr = InetAddress.getLocalHost().getHostAddress();
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		port = AppUtil.getPort();
	}

	public void startThread() {
		if (!isthreadStarted) {
			rpcServerService.start();
			isthreadStarted = true;
		}
	}

	@Override
	public void run() {
		RpcServer();
	}

	private void RpcServer() {

		DatagramSocket rpcSocket = null;
		try {
			LOGGER.info("-------------------------------- RPCSERVER starts : ----- listen port : "
					+ port);
			// rpcSocket = new DatagramSocket(port);
			rpcSocket = AppUtil.getDatagramSkt();
		} catch (Exception e) {
			e.printStackTrace();
		}
		byte[] outBuf = new byte[1024];
		byte[] inBuf = new byte[1024];

		try {
			while (true) {

				DatagramPacket recvPkt = new DatagramPacket(inBuf, inBuf.length);
				rpcSocket.receive(recvPkt);
				InetAddress returnAddr = recvPkt.getAddress();
				int returnPort = recvPkt.getPort();

				LOGGER.info(" RPC Server : receiving the packt : port"
						+ returnPort);

				LOGGER.info("---------------- RPC Server : RECEIVED PKT -------------"
						+ recvPkt.getData());
				outBuf = computeResponse(recvPkt.getData(), recvPkt.getLength());
				LOGGER.info(" RPC Server : receiving the packt :  response"
						+ outBuf);
				if (outBuf != null && outBuf.length > 0) {
					DatagramPacket sendPkt = new DatagramPacket(outBuf,
							outBuf.length, returnAddr, returnPort);
					rpcSocket.send(sendPkt);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		rpcSocket.close();
	}

	private byte[] computeResponse(byte[] packetData, int packetLength) {
		LOGGER.info("-------- computeResponse(): starts... ");
		if (packetData == null || packetData.length == 0 || packetLength <= 0) {
			LOGGER.error("computeResponse():ERROR: the packet data is empty.");
			return null;
		}
		String response = new String(packetData);
		LOGGER.info("-------- computeResponse(): packet data" + response);
		String tokens[] = (response.trim())
				.split(LSIConstants.PACKET_DATA_DELIMITER);
		if (tokens == null || tokens.length == 0) {
			LOGGER.error("ERROR: computeResponse(): The packet is empty.");
			return null;
		}
		// ////////////
		for (String token : tokens) {
			LOGGER.info(" *888888888888888888888888888 ---------------- token : "
					+ token);
		}
		// //////////
		String callID = tokens[0];
		String operationType = tokens[1];
		LOGGER.info("-------- computeResponse(): operation type"
				+ operationType.trim() + "---");
		if (LSIConstants.GET_MESSG_TYPE.equals(operationType.trim())) {
			String sessionID = tokens[2];
			String sessionVerNum = tokens[3];
			SessionTableData sessionData = brickMgr.getSessionData(sessionID,
					sessionVerNum);
			if (sessionData == null) {
				// this server does not have the session data. send negative ACK
				StringBuffer resBuffer = new StringBuffer("");
				resBuffer.append(callID);
				resBuffer.append(LSIConstants.PACKET_DATA_DELIMITER);
				resBuffer.append(LSIConstants.NEG_ACK_MESSG_TYPE);
				return resBuffer.toString().getBytes();
			} else {
				StringBuffer resBuffer = new StringBuffer("");
				resBuffer.append(callID);
				resBuffer.append(LSIConstants.PACKET_DATA_DELIMITER);
				resBuffer.append(LSIConstants.DATA_MESSG_TYPE);
				resBuffer.append(LSIConstants.PACKET_DATA_DELIMITER);
				resBuffer.append(sessionData.getSessionID());
				resBuffer.append(LSIConstants.PACKET_DATA_DELIMITER);
				resBuffer.append(sessionData.getVersionNumber());
				resBuffer.append(LSIConstants.PACKET_DATA_DELIMITER);
				resBuffer.append(sessionData.getSessionCreationTime());
				resBuffer.append(LSIConstants.PACKET_DATA_DELIMITER);
				resBuffer.append(sessionData.getCounter());
				resBuffer.append(LSIConstants.PACKET_DATA_DELIMITER);
				resBuffer.append(sessionData.getSessionVal());
				return resBuffer.toString().getBytes();
			}
		} else if (LSIConstants.PUT_MESSG_TYPE.equals(operationType.trim())) {
			// it is a put message.. save the session data in the brick and send
			// ACK message
			String sessionID = tokens[2];
			String sessionVerNum = tokens[3];
			String sessionCreationTime = tokens[4];
			// TODO what is counter value
			int counter = Integer.parseInt(tokens[5]);
			String sessionVal = tokens[6];
			brickMgr.saveServerData(sessionID, sessionVerNum,
					sessionCreationTime, counter, sessionVal);
			StringBuffer resBuffer = new StringBuffer("");
			resBuffer.append(callID);
			resBuffer.append(LSIConstants.PACKET_DATA_DELIMITER);
			resBuffer.append(LSIConstants.ACK_MESSG_TYPE);
			// try {
			resBuffer.append(LSIConstants.PACKET_DATA_DELIMITER);
			resBuffer.append(ipAddr);
			resBuffer.append(LSIConstants.PACKET_DATA_DELIMITER);
			resBuffer.append(port);
			// } catch (LSIException exp) {
			// LOGGER.error("ComputeResponse: ERROR: Error while fetching the IP address and the port");
			// }
			return resBuffer.toString().getBytes();
		} else if (LSIConstants.DEL_MESSG_TYPE.equals(operationType.trim())) {
			String sessionID = tokens[2];
			brickMgr.deleteSessionData(sessionID);
			StringBuffer resBuffer = new StringBuffer("");
			resBuffer.append(LSIConstants.ACK_MESSG_TYPE);
			return resBuffer.toString().getBytes();
		} else if (LSIConstants.ACK_MESSG_TYPE.equals(operationType.trim())) {
			return (new StringBuffer("").toString().getBytes());
		} else if (LSIConstants.ALIVE_MESSG_TYPE.equals(operationType.trim())) {
			// Ignore the ALIVE messages time being
			return (new StringBuffer("").toString().getBytes());
		} else if (LSIConstants.PROBE_MESSG_TYPE.equals(operationType.trim())) {
			LOGGER.info("-------- computeResponse(): PROBBING... ");
			StringBuffer resBuffer = new StringBuffer("");
			resBuffer.append(callID);
			resBuffer.append(LSIConstants.PACKET_DATA_DELIMITER);
			resBuffer.append(LSIConstants.ALIVE_MESSG_TYPE);
			LOGGER.info("-------- computeResponse(): response buffer.. "
					+ resBuffer.toString());
			return resBuffer.toString().getBytes();
		} else if (LSIConstants.NEG_ACK_MESSG_TYPE.equals(operationType.trim())) {
			// Ignore the NEG ACK messages
			return (new StringBuffer("").toString().getBytes());
		}
		LOGGER.info("-------- computeResponse(): ends... ");
		return null;
	}
}